<ion-item-group [formGroup]="formGroup">
  <div *ngFor="let entry of formGroup.controls | keyvalue: asIsOrder">
    <div *ngIf="objectSpec[entry.key] as spec">
      <!-- string or number -->
      <ng-container *ngIf="spec.type === 'string' || spec.type === 'number'">
        <!-- label -->
        <h4 class="input-label">
          <form-label
            [data]="{
              name: spec.name,
              description: spec.description,
              new: original?.[entry.key] === undefined,
              edited: entry.value.dirty,
              required: !spec.nullable
            }"
          ></form-label>
        </h4>
        <ion-item [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'">
          <ion-textarea
            *ngIf="spec.type === 'string' && spec.textarea; else notTextArea"
            [placeholder]="spec.placeholder || 'Enter ' + spec.name"
            [formControlName]="entry.key"
            (ionFocus)="presentAlertChangeWarning(entry.key, spec)"
            (ionChange)="handleInputChange()"
          ></ion-textarea>
          <ng-template #notTextArea>
            <ion-input
              type="text"
              [inputmode]="spec.type === 'number' ? 'tel' : 'text'"
              [class.redacted]="
                spec.type === 'string' &&
                entry.value.value &&
                spec.masked &&
                !unmasked[entry.key]
              "
              [placeholder]="spec.placeholder || 'Enter ' + spec.name"
              [formControlName]="entry.key"
              (ionFocus)="presentAlertChangeWarning(entry.key, spec)"
              (ionChange)="handleInputChange()"
            ></ion-input>
          </ng-template>
          <ion-button
            *ngIf="spec.type === 'string' && spec.masked"
            slot="end"
            fill="clear"
            color="light"
            (click)="unmasked[entry.key] = !unmasked[entry.key]"
          >
            <ion-icon
              slot="icon-only"
              [name]="unmasked[entry.key] ? 'eye-off-outline' : 'eye-outline'"
              size="small"
            ></ion-icon>
          </ion-button>
          <ion-note
            *ngIf="spec.type === 'number' && spec.units"
            slot="end"
            color="light"
            style="font-size: medium"
          >
            {{ spec.units }}
          </ion-note>
        </ion-item>
        <p class="error-message">
          <span *ngIf="(formGroup | getControl: entry.key).errors as errors">
            {{ errors | getError: $any(spec)['pattern-description'] }}
          </span>
        </p>
      </ng-container>
      <!-- boolean or enum -->
      <ion-item
        *ngIf="spec.type === 'boolean' || spec.type === 'enum'"
        style="--padding-start: 0"
      >
        <ion-button
          *ngIf="spec.description"
          fill="clear"
          (click)="presentAlertBoolEnumDescription($event, spec)"
          style="--padding-start: 0"
        >
          <ion-icon
            name="help-circle-outline"
            slot="icon-only"
            size="small"
          ></ion-icon>
        </ion-button>
        <ion-label>
          <b>
            {{ spec.name }}
            <ion-text
              *ngIf="original?.[entry.key] === undefined"
              color="success"
            >
              (New)
            </ion-text>
            <ion-text *ngIf="entry.value.dirty" color="warning">
              (Edited)
            </ion-text>
          </b>
        </ion-label>
        <!-- boolean -->
        <ion-toggle
          *ngIf="spec.type === 'boolean'"
          slot="end"
          [formControlName]="entry.key"
          (ionChange)="handleBooleanChange(entry.key, spec)"
        ></ion-toggle>
        <!-- enum -->
        <!-- class enter-click disables the enter click on the modal behind the select -->
        <ion-select
          *ngIf="spec.type === 'enum' && formGroup.get(entry.key) as control"
          [interfaceOptions]="{
            message: spec.warning | toWarningText,
            cssClass: 'enter-click'
          }"
          slot="end"
          placeholder="Select"
          [formControlName]="entry.key"
          [selectedText]="spec['value-names'][control.value]"
        >
          <ion-select-option
            *ngFor="let option of spec.values"
            [value]="option"
          >
            {{ spec['value-names'][option] }}
          </ion-select-option>
        </ion-select>
      </ion-item>
      <!-- object -->
      <ng-container *ngIf="spec.type === 'object'">
        <!-- label -->
        <ion-item-divider
          (click)="toggleExpandObject(entry.key)"
          style="cursor: pointer"
          [class.error-border]="entry.value.invalid"
        >
          <form-label
            [data]="{
              name: spec.name,
              description: spec.description,
              new: original?.[entry.key] === undefined,
              edited: entry.value.dirty,
              newOptions: objectDisplay[entry.key].hasNewOptions
            }"
          ></form-label>
          <ion-icon
            slot="end"
            name="chevron-up"
            [color]="entry.value.invalid ? 'danger' : undefined"
            [ngStyle]="{
              transform: objectDisplay[entry.key].expanded
                ? 'rotate(0deg)'
                : 'rotate(180deg)',
              transition: 'transform 0.42s ease-out'
            }"
          ></ion-icon>
        </ion-item-divider>
        <!-- body -->
        <tui-expand
          [expanded]="objectDisplay[entry.key].expanded"
          [id]="objectId | toElementId: entry.key"
        >
          <div class="nested-wrapper">
            <form-object
              [objectSpec]="spec.spec"
              [formGroup]="$any(entry.value)"
              [current]="current?.[entry.key]"
              [original]="original?.[entry.key]"
              (hasNewOptions)="setHasNew(entry.key)"
            ></form-object>
          </div>
        </tui-expand>
      </ng-container>
      <!-- union -->
      <form-union
        *ngIf="spec.type === 'union'"
        [spec]="spec"
        [formGroup]="$any(entry.value)"
        [current]="current?.[entry.key]"
        [original]="original?.[entry.key]"
      ></form-union>
      <!-- list (not enum) -->
      <ng-container *ngIf="spec.type === 'list' && spec.subtype !== 'enum'">
        <ng-container
          *ngIf="formGroup.get(entry.key) as formArr"
          [formArrayName]="entry.key"
        >
          <!-- label -->
          <ion-item-divider [class.error-border]="entry.value.invalid">
            <form-label
              [data]="{
                name: spec.name,
                description: spec.description,
                new: original?.[entry.key] === undefined,
                edited: entry.value.dirty,
                required: !!(spec.range | toRange).min
              }"
            ></form-label>
            <ion-button
              strong
              fill="clear"
              color="dark"
              slot="end"
              (click)="addListItemWrapper(entry.key, spec)"
            >
              <ion-icon slot="start" name="add"></ion-icon>
              Add
            </ion-button>
          </ion-item-divider>
          <p class="error-message" style="margin-bottom: 8px">
            <span *ngIf="(formGroup | getControl: entry.key).errors as errors">
              {{ errors | getError }}
            </span>
          </p>
          <!-- body -->
          <div class="nested-wrapper">
            <div
              *ngFor="
                let abstractControl of $any(formArr).controls;
                let i = index
              "
            >
              <!-- object or union -->
              <ng-container
                *ngIf="spec.subtype === 'object' || spec.subtype === 'union'"
              >
                <!-- object/union label -->
                <ion-item
                  button
                  (click)="toggleExpandListObject(entry.key, i)"
                  [class.error-border]="abstractControl.invalid"
                >
                  <form-label
                    [data]="{
                      name:
                        objectListDisplay[entry.key][i].displayAs ||
                        'Entry ' + (i + 1),
                      new: false,
                      edited: abstractControl.dirty
                    }"
                  ></form-label>
                  <ion-icon
                    slot="end"
                    name="chevron-up"
                    [color]="abstractControl.invalid ? 'danger' : undefined"
                    [ngStyle]="{
                      transform: objectListDisplay[entry.key][i].expanded
                        ? 'rotate(0deg)'
                        : 'rotate(180deg)',
                      transition: 'transform 0.42s ease-out'
                    }"
                  ></ion-icon>
                </ion-item>
                <!-- object/union body -->
                <tui-expand
                  style="padding-left: 24px"
                  [expanded]="objectListDisplay[entry.key][i].expanded"
                  [id]="objectId | toElementId: entry.key:i"
                >
                  <form-object
                    *ngIf="spec.subtype === 'object'"
                    [objectSpec]="$any(spec.spec).spec"
                    [formGroup]="abstractControl"
                    [current]="current?.[entry.key]?.[i]"
                    [original]="original?.[entry.key]?.[i]"
                    (onInputChange)="
                      updateLabel(entry.key, i, $any(spec.spec)['display-as'])
                    "
                  ></form-object>
                  <form-union
                    *ngIf="spec.subtype === 'union'"
                    [spec]="$any(spec.spec)"
                    [formGroup]="abstractControl"
                    [current]="current?.[entry.key]?.[i]"
                    [original]="original?.[entry.key]?.[i]"
                    (onInputChange)="
                      updateLabel(entry.key, i, $any(spec.spec)['display-as'])
                    "
                  ></form-union>
                  <div style="text-align: right; padding-top: 12px">
                    <ion-button
                      fill="clear"
                      (click)="presentAlertDelete(entry.key, i)"
                      color="danger"
                    >
                      <ion-icon slot="start" name="close"></ion-icon>
                      Delete
                    </ion-button>
                  </div>
                </tui-expand>
              </ng-container>
              <!-- string or number -->
              <div
                *ngIf="spec.subtype === 'string' || spec.subtype === 'number'"
                [id]="objectId | toElementId: entry.key:i"
              >
                <ion-item
                  [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'"
                >
                  <ion-input
                    type="text"
                    [inputmode]="spec.subtype === 'number' ? 'tel' : 'text'"
                    [placeholder]="
                      $any(spec.spec).placeholder || 'Enter ' + spec.name
                    "
                    [formControlName]="i"
                  ></ion-input>
                  <ion-button
                    strong
                    fill="clear"
                    slot="end"
                    color="danger"
                    (click)="presentAlertDelete(entry.key, i)"
                  >
                    <ion-icon slot="icon-only" name="close"></ion-icon>
                  </ion-button>
                </ion-item>
                <p class="error-message">
                  <span
                    *ngIf="
                      (formGroup | getControl: entry.key:i).errors as errors
                    "
                  >
                    {{ errors | getError: $any(spec)['pattern-description'] }}
                  </span>
                </p>
              </div>
            </div>
          </div>
        </ng-container>
      </ng-container>
      <!-- list (enum) -->
      <ng-container *ngIf="spec.type === 'list' && spec.subtype === 'enum'">
        <ng-container
          *ngIf="formGroup.get(entry.key) as formArr"
          [formArrayName]="entry.key"
        >
          <!-- label -->
          <p class="input-label">
            <form-label
              [data]="{
                name: spec.name,
                description: spec.description,
                new: original?.[entry.key] === undefined,
                edited: entry.value.dirty
              }"
            ></form-label>
          </p>
          <!-- list -->
          <ion-item
            button
            detail="false"
            color="dark"
            (click)="presentModalEnumList(entry.key, $any(spec), formArr.value)"
          >
            <ion-label style="white-space: nowrap !important">
              <h2>{{ formArr.value | toEnumListDisplay: $any(spec.spec) }}</h2>
            </ion-label>
            <ion-button slot="end" fill="clear" color="light">
              <ion-icon slot="icon-only" name="chevron-down"></ion-icon>
            </ion-button>
          </ion-item>
          <p class="error-message">
            <span *ngIf="(formGroup | getControl: entry.key).errors as errors">
              {{ errors | getError }}
            </span>
          </p>
        </ng-container>
      </ng-container>
    </div>
  </div>
</ion-item-group>
